/********************************************************************************
    Warcraft 3 Viewer - Utility to view models and textures from Warcraft 3
    Copyright (C) 2002  David GRIMBICHLER (theprophet@wanadoo.Fr)
	http://www.xeberon.net

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
********************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <math.h>
#include <time.h>
#include <io.h>

#include <gl/gl.h>
#include <gl/glu.h>

#include "math.h"

#include "mdx.h"

#include "blp.h"


extern DWORD FromMPQ(char*filename, char*& buffer);
extern LPDIRECT3D8 g_D3D;
extern IDirect3DDevice8 *g_d3d_device;
extern char UnClamp(float f);


extern void FatalError(const char *error_msg);
extern BOOL g_showmeshes[MAX_CHUNKS];


WarCraft_ModelX* singleton = NULL;


WarCraft_ModelX::WarCraft_ModelX(void) :
	mpData(NULL),
	mSize(0),
	mLoaded(false)
{
	singleton = this;
}

void WarCraft_ModelX::Free(void)
{
	if(mTextures.mNumTextures)
	{
		for(int k=0; k<mTextures.mNumTextures; k++)
		{
			if(mTextures.mGLBind[k])
				mTextures.mGLBind[k]->Release();
		}

	}

	if(mpData.c!=NULL)
		delete[] mpData.c;

	ZeroMemory(this,sizeof(WarCraft_ModelX));
}

WarCraft_ModelX::~WarCraft_ModelX(void)
{
	Free();
	singleton = NULL;
}

void WarCraft_ModelX::Render(void)
{	
	if(false == mLoaded)
		return;

	mGeometry.Render();
}

void WarCraft_ModelX::Load(char* inName)
{
	toregistre("Attempting to read file %s...",inName);
	
	mSize = FromMPQ(inName,mpData.c);
	
	if(!mSize)
		return;


	TypePointer p(mpData.p);

	if('MDLX' != TAG(*p.dw))
	{
		toregistre("Missing model tag : '%c%c%c%c' instead of 'MDLX'",p.c[0],p.c[1],p.c[2],p.c[3]);
		return;
	}
	p.dw++;

	while(p.c < mpData.c+mSize)
	{
		switch(TAG(*p.dw))
		{
		case 'BONE':
			{
				p.dw++;
				int size = *p.dw++;


				p.c += size;
			}
			break;

		case 'VERS': 
			{
				p.dw++;
				int size = *p.dw++;

				mVersion.Read(p,size);

				if( !mVersion.IsValid() )
					return;


				p.c += size;
			}
			break;

		case 'MODL':
			{
				p.dw++;
				int size = *p.dw++;

				mModel.Read(p,size);
		
				p.c += size;
			}
			break;

		case 'GEOS': 
			{
				p.dw++;
				int size = *p.dw++;

				mGeometry.Read(p,size);

					
				p.c += size;
			}
			break;

		case 'SEQS':
			{
				p.dw++;
				int size = *p.dw++;

				mSequences.Read(p,size);

				
				p.c += size; 
			}
			break;

		case 'TEXS': 
			{
				p.dw++;
				int size = *p.dw++;

				mTextures.Read(p,size);

				mTextures.Print();
				
				p.c += size; 				
			}
			break;

		case 'MTLS':
			{
				p.dw++;
				int size = *p.dw++;

				mMaterials.Read(p,size);

				mMaterials.Print();
				
				p.c += size; 				
			}
			break;

		default:
			{
				toregistre("Unknown tag found : '%c%c%c%c'",p.c[0],p.c[1],p.c[2],p.c[3]);
				p.dw++;
				int size = *p.dw++;
				p.c += size; 
			}
			break;
		}
	}

	mLoaded = true;
}


void mdxGeoChunk::Read(TypePointer inP,int inSize)
{
	TypePointer p(inP);

	int n = 0;

	while((p.c < inP.c+inSize) && (n<8))
	{
		switch(TAG(*p.dw))
		{
		case 'VRTX': 
			{
				p.dw++;
				mNumVertices = *p.dw++;

				mVertices = p.v3;

				p.c += mNumVertices*12; 
				++n;
			}
			break;

		case 'NRMS':
			{
				p.dw++;
				int NumNormals = *p.dw++;

				if(mNumVertices != NumNormals)
				{
					toregistre("NumVertices and NumNormals does not match !!!!!!!!!");
				}

				mNormals = p.v3;

				p.c += NumNormals*12; 
				++n;
			}
			break;

		case 'PTYP': 
			{
				p.dw++;
				int size = *p.dw++;
			
				if(*p.dw != 4) 
				{
					toregistre("Unknown polytype !!!!!!!!!!!");
					return;
				}

				p.c += size*4; 
				++n;
			}
			break;

		case 'PCNT': 
			{
				p.dw++;
				int size = *p.dw++;
				mPolyCount = (*p.dw)/3;
			
				p.c += size*4; 
				++n;
			}
			break;

		case 'PVTX':
			{
				p.dw++;
				int size = *p.dw++;
				mTriangles = p.tri;

				if(size/3 != mPolyCount)
				{
					toregistre("Polycount and size of triangle block do not match!!!!!!!!!!!");
					return;
				}
			
				p.c += size*2;
				++n;
			}
			break;

		case 'GNDX':
				
			{
				p.dw++;
				int size = *p.dw++;

				if(mNumVertices != size)
				{
					toregistre("NumVertices and vertex group index count do not match !!!!!!!!!");
				}

				mVertexGroupIndices = p.c;

				p.c += size; 
				++n;
			}
			break;
			
		case 'MTGC':
				
			{
				p.dw++;
				int size = *p.dw++;
			
				p.c += size*4; 
				++n;
			}
			break;

		case 'MATS':
			{
				p.dw++;
				int size = *p.dw++;
			
				p.c += size*4;  
				++n;
			}
			break;

		default:
			{
				toregistre("Unknown geotag found : '%c%c%c%c'",p.c[0],p.c[1],p.c[2],p.c[3]);
				p.dw++;
				int size = *p.dw++;
				p.c += size; 
			}
			break;
		}
	}

	if(p.c < inP.c+inSize)
	{
		mMaterial = *p.dw++;
		mUnk2 = *p.dw++;
		mUnk3 = *p.dw++;

		mUnk4 = *p.f++;
		mMins = p.v3++;
		mMaxs = p.v3++;

		mSomeCount = *p.dw++;

		for(int i=0;i<mSomeCount;++i)
		{
			p.f++; 
			p.v3++; 
			p.v3++;
		}

		if(TAG(*p.dw) == 'UVAS' )
		{
			p.dw++;

			int size = *p.dw++;

			if(TAG(*p.dw) == 'UVBS' )
			{
				p.dw++;
				int size = *p.dw++;

				mUVs = p.v2;
				
				p.c += size*8;
			}
			else
				toregistre("UVBS geotag missing, instead: '%c%c%c%c'",p.c[0],p.c[1],p.c[2],p.c[3]);
		}
		else
			toregistre("Unknown geotag found : '%c%c%c%c'",p.c[0],p.c[1],p.c[2],p.c[3]);
	}
	else
		toregistre("No data remainder in geoset...");
}



void mdxGeometry::Read(TypePointer inP,int inSize)
{
	if(mLoaded)
	{
		toregistre("Duplicate GEOS tag... ignoring");
		return;
	}
	mLoaded = true;

	TypePointer p(inP);

	while(p.c<inP.c+inSize)
	{
		int size = (*p.dw++)-4;

		mChunks[mNumChunks].Read(p,size);

		p.c += size;

		++mNumChunks;
	}
}

void mdxGeometry::Render(void)
{
	for(int i=0;i<mNumChunks;++i)
	{
		if(g_showmeshes[i] == TRUE)	mChunks[i].Render();
	}
}

void mdxGeometry::Print(void)
{
	toregistre("");
	toregistre("NumGeoChunks : %i",mNumChunks);
	for(int i=0;i<mNumChunks;++i)
	{
		toregistre("GeoChunk %i",i);
		mChunks[i].Print();
	}
}



void mdxGeoChunk::Render(void)
{
	mdxMaterial& mat = singleton->mMaterials.mMaterials[mMaterial];

	for(int l=0;l<mat.mNumLayers;++l)
	{
		if(l<mat.mNumLayers-1)
			g_d3d_device->SetRenderState(D3DRS_LIGHTING,FALSE);
		else
			g_d3d_device->SetRenderState(D3DRS_LIGHTING,TRUE);

		if(mat.mLayers[l].mBlendMode ==1 )
		{
			g_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
			g_d3d_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
			g_d3d_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
			g_d3d_device->SetRenderState(D3DRS_ALPHATESTENABLE, TRUE);
			g_d3d_device->SetRenderState(D3DRS_ALPHAREF, (DWORD)0x0000000BE);
			g_d3d_device->SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATER);
		}else
		if(mat.mLayers[l].mBlendMode ==2 )
		{
			g_d3d_device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
			g_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
			g_d3d_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
			g_d3d_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
		}else
		if(mat.mLayers[l].mBlendMode ==3 )
		{
			g_d3d_device->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
			g_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
			if(gAllGeom==false)
			{
				g_d3d_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ZERO);
				g_d3d_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
			}
			else
			{
				g_d3d_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_ONE);
				g_d3d_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_ONE);
			}
			
		}else
		{
			g_d3d_device->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
		}

		my_vertex v[3];
		for(int i=0;i<mPolyCount;++i)
		{			
			for(int k=0; k<3; ++k)
			{
				v[k].x = mVertices[mTriangles[i].ind[k]].v[0];
				v[k].y = mVertices[mTriangles[i].ind[k]].v[1];
				v[k].z = mVertices[mTriangles[i].ind[k]].v[2];
				v[k].nx = mNormals[mTriangles[i].ind[k]].v[0];
				v[k].ny = mNormals[mTriangles[i].ind[k]].v[1];
				v[k].nz = mNormals[mTriangles[i].ind[k]].v[2];
				v[k].colour = (gTextured==FALSE ? D3DCOLOR_COLORVALUE(mUVs[mTriangles[i].ind[k]][0],mUVs[mTriangles[i].ind[k]][1], 0, 1.0f) : D3DCOLOR_COLORVALUE(0.7f,0.7f, 0.7f, 1.0f));
				v[k].tu = mUVs[mTriangles[i].ind[k]].v[0];
				v[k].tv = mUVs[mTriangles[i].ind[k]].v[1];
			}
				HRESULT hr;
				IDirect3DVertexBuffer8 *g_vb=NULL;
			   hr=g_d3d_device->CreateVertexBuffer(3*sizeof(my_vertex), D3DUSAGE_WRITEONLY, D3D8T_CUSTOMVERTEX, D3DPOOL_MANAGED, &g_vb);
			   if(FAILED(hr)){
				  FatalError("Error creating vertex buffer");
			   }

			   unsigned char *vb_vertices;
			   hr=g_vb->Lock(0, 0, &vb_vertices, 0);
			   if(FAILED(hr)){
				  FatalError("Error Locking triangle buffer");
			   }
   
			   memcpy(vb_vertices, v, sizeof(v) );
			   g_vb->Unlock();
				g_d3d_device->SetVertexShader(D3D8T_CUSTOMVERTEX);
				g_d3d_device->SetStreamSource(0,g_vb,sizeof(my_vertex));
				if(gTextured==TRUE)
					g_d3d_device->SetTexture(0, singleton->mTextures.mGLBind[(mat.mLayers[l].mTexture > 127 ? 0 : mat.mLayers[l].mTexture)]);
				else
					g_d3d_device->SetTexture(0, NULL);

				g_d3d_device->DrawPrimitive(D3DPT_TRIANGLESTRIP,0,1);
				g_d3d_device->SetTexture(0, NULL);

			g_vb->Release();
		}
	}
}

void mdxGeoChunk::Print(void)
{
	toregistre("  mNumVertices : %i",mNumVertices);
	toregistre("  mPolyCount   : %i",mPolyCount);
	toregistre("  mMaterial    : %i",mMaterial);
	toregistre("  mUnk2        : %i",mUnk2);
	toregistre("  mUnk3        : %i",mUnk3);
	toregistre("  mUnk4        : %f",mUnk4);
	toregistre("  mMins        : %f|%f|%f",(*mMins)[0],(*mMins)[1],(*mMins)[2]);
	toregistre("  mMaxs        : %f|%f|%f",(*mMaxs)[0],(*mMaxs)[1],(*mMaxs)[2]);
	toregistre("  mSomeCount   : %i",mSomeCount);	
}







void mdxModel::Read(TypePointer inP,int inSize)
{
	if(mName)
	{
		toregistre("Duplicate MODL tag... ignoring");
		return;
	}

	if( inSize != 372)
		return;

	mName  = inP.c;

	inP.c += 80;

	mName2 = inP.c;

	inP.c += MAX_PATH;

	mUnk1 = *inP.f;

	inP.f += 1;

	mMins = inP.v3;

	inP.v3 += 1;

	mMaxs = inP.v3;

	inP.v3 += 1;

	mUnk2 = *inP.dw;
}

void mdxModel::Print(void)
{
	if(!mName)
	{
		toregistre("Error in MODL tag");
		return;
	}

	toregistre("");
	toregistre("Name  : \'%s\'",mName);
	toregistre("Name2 : \'%s\'",mName2);
	toregistre("Unk1  : %f",mUnk1);
	toregistre("Mins  : %f|%f|%f",(*mMins)[0],(*mMins)[1],(*mMins)[2]);
	toregistre("Maxs  : %f|%f|%f",(*mMaxs)[0],(*mMaxs)[1],(*mMaxs)[2]);
	toregistre("Unk2  : %i",mUnk2);
}








void mdxVersion::Read(TypePointer inP,int inSize)
{
	if(mVersion)
	{
		toregistre("Duplicate VERS tag... ignoring");
		return;
	}

	if(inSize != 4)
		return;

	mVersion = *inP.dw;
}


BOOL mdxVersion::IsValid(void)
{
	if(mVersion == 800)
		return true;	

	return false;
}

void mdxVersion::Print(void)
{
	toregistre("");
	toregistre("Version : %i",mVersion);
}


void mdxTextures::Read(TypePointer inP,int inSize)
{
	if(mLoaded)
	{
		toregistre("Duplicate TEXS tag... ignoring");
		return;
	}
	mLoaded = true;

	TypePointer p(inP);

	mTextures = p.tex;

	mNumTextures = inSize / sizeof(Texture);

	for(int i=0; i<128 && i<mNumTextures ; ++i)
	{
		DWORD width,height,type,subtype;
		LoadBLP(mTextures[i].mName,width,height,type,subtype, &mGLBind[i]);
	}
}

void mdxTextures::Print(void)
{
	toregistre("");
	toregistre("NumTextures : %i",mNumTextures);

	for(int i=0;i<mNumTextures;++i)
	{
		toregistre("Texture %i:",i);
		toregistre("  Unk1 : %i",mTextures[i].mUnk1);
		toregistre("  Name : \'%s\'",mTextures[i].mName);
		toregistre("  Unk2 : %i",mTextures[i].mUnk2);
	}
}







void mdxSequence::Read(TypePointer inP,int inSize)
{
	if(mLoaded)
	{
		toregistre("Duplicate SEQS tag... ignoring");
		return;
	}
	mLoaded = true;

	TypePointer p(inP);

	mSequences = p.seq;

	mNumSequences = inSize / sizeof(SequenceName);
}

void mdxSequence::Print(void)
{
	toregistre("");
	toregistre("NumSequences : %i",mNumSequences);

	for(int i=0;i<mNumSequences;++i)
	{
		toregistre("Sequence %i:",i);
		toregistre("  Name : \'%s\'",mSequences[i].mName);
		toregistre("  Unk1 : %i",mSequences[i].mUnk1);
		toregistre("  Unk2 : %i",mSequences[i].mUnk2);
		toregistre("  Unk3 : %f",mSequences[i].mUnk3);
		toregistre("  Unk4 : %i",mSequences[i].mUnk4);
		toregistre("  Unk5 : %f",mSequences[i].mUnk5);
		toregistre("  Unk6 : %i",mSequences[i].mUnk6);
		toregistre("  Unk7 : %f",mSequences[i].mUnk7);
		toregistre("  Mins : %f|%f|%f",mSequences->mMins[0],mSequences->mMins[1],mSequences->mMins[2]);
		toregistre("  Maxs : %f|%f|%f",mSequences->mMaxs[0],mSequences->mMaxs[1],mSequences->mMaxs[2]);
	}
}





void mdxMaterialMap::Read(TypePointer inP,int inSize)
{
	if(mLoaded)
	{
		toregistre("Duplicate MATS tag... ignoring");
		return;
	}
	mLoaded = true;

	TypePointer p(inP);

	mNumMaterials = NULL;

	while(p.c < inP.c+inSize)
	{
		int size = (*p.dw++)-4;

		mMaterials[mNumMaterials++].Read(p,size);

		p.c+=size;
	}
}

void mdxMaterialMap::Print(void)
{
	toregistre("");
	toregistre("NumMaterials : %i",mNumMaterials);

	for(int i=0;i<mNumMaterials;++i)
	{
		toregistre(" Material %i",i);
		mMaterials[i].Print();
	}

}





void mdxMaterial::Read(TypePointer inP,int inSize)
{
	TypePointer p(inP);

	mUnk1 = *p.dw++;
	mUnk2 = *p.dw++;

	if( TAG(*p.dw) != 'LAYS' )
	{
		toregistre("Warning, \'LAYS\' expected...");
		return;
	}
	p.dw++;

	mNumLayers = *p.dw++;

	for(int i=0;i<mNumLayers;++i)
	{
		mLayers[i].mLayerSize = p.dw[0];
		mLayers[i].mBlendMode = p.dw[1];
		mLayers[i].mUnk3 = p.dw[2];
		mLayers[i].mTexture = p.dw[3];
		mLayers[i].mUnk5 = p.dw[4];
		mLayers[i].mUnk6 = p.dw[5];
		mLayers[i].mUnk7 = p.f[6];
		

		if(TAG(p.dw[7]) == 'KMTA')
		{
			mLayers[i].mKMTA = true;
		} else
		{
			mLayers[i].mKMTA = false;
		}
		p.c += mLayers[i].mLayerSize;
	}
}

void mdxMaterial::Print(void)
{
	toregistre("  mUnk1     : %i",mUnk1);
	toregistre("  mUnk2     : %i",mUnk2);

	for(int i=0;i<mNumLayers;++i)
	{
		toregistre("   Layer %i",i);

		toregistre("    mBlendMode: %i",mLayers[i].mBlendMode);
		toregistre("    mUnk3     : %i",mLayers[i].mUnk3);
		toregistre("    mTexture  : %i",mLayers[i].mTexture);
		toregistre("    mUnk5     : %i",mLayers[i].mUnk5);
		toregistre("    mUnk6     : %i",mLayers[i].mUnk6);
		toregistre("    mUnk7     : %f",mLayers[i].mUnk7);
		if(mLayers[i].mKMTA)
			toregistre("    has a KMTA block.");
	}
}



